package com.cloud.logger.zchain.support;

import com.cloud.logger.zchain.annotation.ZchainScheduled;
import com.cloud.logger.zchain.annotation.Schedules;
import com.cloud.logger.zchain.core.CronTriggerBean;
import com.cloud.logger.zchain.core.JobDetailProxyBean;
import com.cloud.logger.zchain.core.SchedulerUtils;
import com.cloud.logger.zchain.core.SimpleTriggerBean;
import com.cloud.logger.zchain.core.common.properties.ServerInfo;
import com.cloud.logger.zchain.core.zookeeper.LeaderElection;
import com.cloud.logger.zchain.core.zookeeper.ZookeeperFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.KeeperException;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.config.NamedBeanHolder;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Administrator on 2017/12/10.
 */
@Slf4j
public class ScheduleAnnotaion implements MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor, Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware, SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {

    private BeanFactory beanFactory;

    private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap(64));

    private final Map<Object, Set<ScheduledTask>> scheduledTasks = new IdentityHashMap(16);

    private ApplicationContext applicationContext;

    private String beanName;

    private StringValueResolver embeddedValueResolver;

    private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();

    private Object scheduler;

    private List<CronTriggerBean> triggers = new ArrayList<>();

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void setBeanName(String s) {
        this.beanName = beanName;
    }

    public void setScheduler(Object scheduler) {
        this.scheduler = scheduler;
    }

    @Override
    public void destroy() throws Exception {
        Map var1 = this.scheduledTasks;
        synchronized(this.scheduledTasks) {
            Collection allTasks = this.scheduledTasks.values();
            Iterator var3 = allTasks.iterator();

            label25:
            while(true) {
                if(var3.hasNext()) {
                    Set tasks = (Set)var3.next();
                    Iterator var5 = tasks.iterator();

                    while(true) {
                        if(!var5.hasNext()) {
                            continue label25;
                        }

                        ScheduledTask task = (ScheduledTask)var5.next();
                        task.cancel();
                    }
                }

                this.scheduledTasks.clear();
                break;
            }
        }

        this.registrar.destroy();
    }

    @Override
    public void afterSingletonsInstantiated() {
        this.nonAnnotatedClasses.clear();
        if(this.applicationContext == null) {
            try {
                this.finishRegistration();
            } catch (SchedulerException e) {
                e.printStackTrace();
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void postProcessBeforeDestruction(Object o, String s) throws BeansException {

    }

    @Override
    public boolean requiresDestruction(Object bean) {
        Map var2 = this.scheduledTasks;
        synchronized(this.scheduledTasks) {
            return this.scheduledTasks.containsKey(bean);
        }
    }

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition rootBeanDefinition, Class<?> aClass, String s) {

    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class targetClass = AopUtils.getTargetClass(bean);
        if(!this.nonAnnotatedClasses.contains(targetClass)) {
            Map annotatedMethods = MethodIntrospector.selectMethods(targetClass, new MethodIntrospector.MetadataLookup() {
                public Set<ZchainScheduled> inspect(Method method) {
                    Set scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, ZchainScheduled.class, Schedules.class);
                    return !scheduledMethods.isEmpty()?scheduledMethods:null;
                }
            });
            if(annotatedMethods.isEmpty()) {
                this.nonAnnotatedClasses.add(targetClass);
                if(log.isTraceEnabled()) {
                    log.trace("No @ZchainScheduled annotations found on bean class: " + bean.getClass());
                }
            } else {
                Iterator var5 = annotatedMethods.entrySet().iterator();

                while(var5.hasNext()) {
                    Map.Entry entry = (Map.Entry)var5.next();
                    Method method = (Method)entry.getKey();
                    Iterator var8 = ((Set)entry.getValue()).iterator();

                    while(var8.hasNext()) {
                        ZchainScheduled zchainScheduled = (ZchainScheduled)var8.next();
                        this.processScheduled(zchainScheduled, method, bean);
                    }
                }

                if(log.isDebugEnabled()) {
                    log.debug(annotatedMethods.size() + " @ZchainScheduled methods processed on bean \'" + beanName + "\': " + annotatedMethods);
                }
            }
        }

        return bean;
    }

    protected void processScheduled(ZchainScheduled zchainScheduled, Method method, Object bean) {
        try {

            String cron = zchainScheduled.cron();
            if(StringUtils.hasText(cron)) {

                JobDetailProxyBean jobDetailProxyBean = new JobDetailProxyBean();
                jobDetailProxyBean.setTargetObject(bean);
                jobDetailProxyBean.setTargetMethod(method);

                CronTriggerBean cronTriggerBean = new  CronTriggerBean();
                cronTriggerBean.setCronExpression(cron);
                cronTriggerBean.setJobDetail(jobDetailProxyBean);


                triggers.add(cronTriggerBean);
            }


        } catch (IllegalArgumentException var26) {
            throw new IllegalStateException("Encountered invalid @ZchainScheduled method \'" + method.getName() + "\': " + var26.getMessage());
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        if(this.beanFactory == null) {
            this.beanFactory = applicationContext;
        }
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if(event.getApplicationContext() == this.applicationContext) {
            try {
                this.finishRegistration();
            } catch (SchedulerException e) {
                e.printStackTrace();
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
        this.embeddedValueResolver = stringValueResolver;
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private void finishRegistration() throws SchedulerException, ParseException {
        // 初始化StdSchedulerFactory
        StdSchedulerFactory schedulerFactory = SchedulerUtils.initStdSchedulerFactory();
        // 获取调度器
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 装载调度器
        for (CronTriggerBean triggerObject : triggers) {
            if (triggerObject instanceof CronTriggerBean) {
                CronTriggerBean cronTriggerBean = (CronTriggerBean) triggerObject;

                // 获取任务代理类对象
                JobDetailProxyBean jobDetailProxyBean = cronTriggerBean.getJobDetail();
                // 装配任务
                JobDetail jobDetail = SchedulerUtils.assemblyJobDetail(jobDetailProxyBean);
                // 设置zooKeeper连接工厂
                jobDetail.getJobDataMap().put("zooKeeperFactory", this.beanFactory.getBean("zookeeperFactory"));
                // 装配触发器
                CronTrigger cronTrigger = SchedulerUtils.assemblyCronTrigger(cronTriggerBean);
                scheduler.scheduleJob(jobDetail, cronTrigger);
                // System.out.println("CronTriggerBean");
            }
        }

        scheduler.start();

        //起一个线程一直选举lead
        try {
            LeaderElection.getLeader();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("调度器已启动");
    }

    private <T> T resolveSchedulerBean(Class<T> schedulerType, boolean byName) {
        if(byName) {
            Object holder1 = this.beanFactory.getBean("taskScheduler", schedulerType);
            if(this.beanFactory instanceof ConfigurableBeanFactory) {
                ((ConfigurableBeanFactory)this.beanFactory).registerDependentBean("taskScheduler", this.beanName);
            }

            return (T) holder1;
        } else if(this.beanFactory instanceof AutowireCapableBeanFactory) {
            NamedBeanHolder holder = ((AutowireCapableBeanFactory)this.beanFactory).resolveNamedBean(schedulerType);
            if(this.beanFactory instanceof ConfigurableBeanFactory) {
                ((ConfigurableBeanFactory)this.beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);
            }

            return (T) holder.getBeanInstance();
        } else {
            return this.beanFactory.getBean(schedulerType);
        }
    }
}